package com.bdqn.service.impl;

import com.bdqn.mapper.SeckillMapper;
import com.bdqn.pojo.goods.Seckill;
import com.bdqn.redis.util.JedisUtil;
import com.bdqn.service.SeckillService;
import com.bdqn.service.ServiceImpl;
import com.bdqn.util.DateUtil;
import com.bdqn.util.RandomUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;

/**
 * 秒杀活动
 *
 * @author LILIBO
 * @since 2022-06-24
 */
@Service
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> implements SeckillService {

    @Resource
    private SeckillMapper seckillMapper;

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        // Lua脚本
        String script = "return redis.call('get', KEYS[1])";
        // 参数列表
        String key = "testKey";
        // 设置key
        jedis.set(key, "10");
        // 执行Lua脚本
        Object result = jedis.eval(script, 1, key);
        System.out.println("Result: " + result);  // 输出结果
        jedis.close();
    }

    /**
     * 商品秒杀
     *
     * @param seckillId
     * @return
     */
    @Override
    @Transactional // 启用事务
    public long viebuy(Long seckillId) {
        // 找到当前秒杀活动
        Seckill seckill = getById(seckillId);

        // 不在活动时间范围
        if (DateUtil.afterNowDate(seckill.getStartTime(), DateUtil.YYYY_MM_DD_HH_MM_SS) || DateUtil.beforeNowDate(seckill.getEndTime(), DateUtil.YYYY_MM_DD_HH_MM_SS)) {
            // 不在活动时间，请继续关注！
            return -1L;
        }
        // 秒杀商品已被抢光
        if (seckill.getStockCount() <= 0) {
            // 秒杀失败，商品已被抢光！
            return 0L;
        }

        // 使用Redis做分布式锁
        String lock_key = "seckill:lock:" + seckillId;
        String user_uuid = RandomUtil.generateUUID(); // AAAA // BBBB
        Jedis jedis = JedisUtil.getJedis();

        // Lua脚本
        String script = "redis.call('setnx', KEYS[1], ARGV[1]) redis.call('expire', KEYS[1], ARGV[2]) return 1";

        long lock_val = 0; // 成功返回1，失败返回0
        try {
            if (jedis != null) {
                // 在Redis中设置一把分布式锁(setnx，如果键已经存在，则不执行任何操作。SETNX实际上意味着“如果不存在则设置”。)
                // lock_val = jedis.setnx(lock_key, String.valueOf(1));
                // 给Redis的分布式锁设置超时时间（为了防止程序出错，锁不释放的问题，兜底方案）
                // jedis.expire(lock_key, 30); // 有问题，在执行完上一行代码，本行代码没执行时服务器宕机，lock_key将无法清除

                // 执行Lua表达式
                lock_val = (Long) jedis.eval(script, 1, lock_key, user_uuid, String.valueOf(30));
                System.out.println("Lua --> " + lock_key);
            }
            if (lock_val == 1) {
                // 模拟业务执行过程（执行10秒，该过程中其他人将抢空，需要重试机制；如果执行30秒，锁被Redis超时释放了，此时有用户再抢到锁可能先于当前用户完成下单，需要给当前用户的锁续期<看门狗机制>）
                Thread.sleep(30 * 1000);
                // 商品秒杀
                long viebuy = seckillMapper.viebuy(seckill.getId(), seckill.getVersion());
                System.out.println("商品秒杀成功~ " + viebuy);
            } else {
                // 重试机制
                System.out.println("没抢到！再抢~~~");
                for (int i = 0; i < 3; i++) {
                    // 间隔1秒，重试3次
                    Thread.sleep(1000);
                    if (jedis != null && !user_uuid.equals(jedis.get(lock_key))) {
                        // 当目前的锁还是被其他人占用时
                        viebuy(seckillId);
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            // 当前用户只能删除自己设置的分布式锁
            if (jedis != null && user_uuid.equals(jedis.get(lock_key))) {
                // 删除分布式锁
                jedis.del(lock_key);
            }
        }
        return seckill.getId();
    }

}
